﻿using CommonSense.UI.Shell.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace CommonSense.UI.Shell.Services
{
    class ModuleService : IModuleService
    {
        private List<Assembly> moduleAssembles;
        private IDictionary<Type, ConstructorInfo> reflectionCache;
        private IDictionary<string, Type> typeNameCache;
        private IDictionary<Type, Type> mvmCache;

        public IDIContainer DIContainer
        {
            get;
            private set;
        }

        public ModuleService(IDIContainer diContainer)
        {
            this.DIContainer = diContainer;
            this.moduleAssembles = new List<Assembly>();

            this.reflectionCache = new Dictionary<Type, ConstructorInfo>();
            this.typeNameCache = new Dictionary<string, Type>();
            this.mvmCache = new Dictionary<Type, Type>();
        }

        public void RegisterAssembly(Assembly assembly)
        {
            this.moduleAssembles.Add(assembly);
        }

        public IEnumerable<IModule> GetModules()
        {
            List<IModule> modules = new List<IModule>();
            Type iModuleType = typeof(IModule);

            foreach (Assembly assembly in this.moduleAssembles)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (iModuleType.IsAssignableFrom(type))
                    {
                        ConstructorInfo constructorInfo = type.GetConstructor(new Type[0]);
                        IModule module = (IModule)constructorInfo.Invoke(new object[0]);
                        module.RegisterServices(this.DIContainer);
                        modules.Add(module);
                    }
                }
            }

            return modules;
        }

        public Type GetTypeByName(string fullTypeName)
        {
            if (!typeNameCache.ContainsKey(fullTypeName))
            {
                foreach (Assembly assembly in this.moduleAssembles)
                {
                    foreach (Type type in assembly.GetTypes())
                    {
                        if (type.FullName == fullTypeName)
                        {
                            this.typeNameCache.Add(fullTypeName, type);
                            break;
                        }
                    }
                }
            }
          
            if (!typeNameCache.ContainsKey(fullTypeName))
            {
                string message = string.Format("Type {0} not found in registered assemblies", fullTypeName);
                throw new InvalidOperationException(message);
            }

            return typeNameCache[fullTypeName];
        }

        public Type GetViewForViewModel(Type viewModelType)
        {
            if (!mvmCache.ContainsKey(viewModelType))
            {
                List<Assembly> assembliesToSearch = new List<Assembly>(this.moduleAssembles);
                assembliesToSearch.Remove(viewModelType.Assembly);
                assembliesToSearch.Insert(0, viewModelType.Assembly);
                string viewTypeName = viewModelType.Name.Replace("Model", string.Empty);

                foreach (Assembly assembly in assembliesToSearch)
                {
                    foreach (Type type in assembly.GetTypes())
                    {
                        if (type.Name == viewTypeName)
                        {
                            this.mvmCache.Add(viewModelType, type);
                            break;
                        }
                    }
                }
            }

            if (!mvmCache.ContainsKey(viewModelType))
            {
                string message = string.Format("View for view model {0} not found in registered assemblies", viewModelType.FullName);
                throw new InvalidOperationException(message);
            }

            return mvmCache[viewModelType];
        }

        public object CreateInstance(Type type)
        {
            ConstructorInfo constructorInfo = this.GetConsturctorInfo(type);
            List<ParameterInfo> constructorParamInfos = constructorInfo.GetParameters().ToList();
            List<object> constructorParams = new List<object>();
            foreach (ParameterInfo paramInfo in constructorParamInfos)
            {
                Type paramType = paramInfo.ParameterType;
                constructorParams.Add(this.DIContainer.Resolve(paramType));
            }

            return constructorInfo.Invoke(constructorParams.ToArray());
        }

        private ConstructorInfo GetConsturctorInfo(Type type)
        {
            if (!this.reflectionCache.ContainsKey(type))
            {
                ConstructorInfo constructorInfo = null;
                List<ConstructorInfo> constructorsInfo = type.GetConstructors().ToList();
                if (constructorsInfo.Count > 1)
                {
                    Type injectionConstructorType = typeof(InjectionConstructorAttribute);
                    constructorInfo = constructorsInfo.FirstOrDefault(c => c.CustomAttributes.Any(a => a.AttributeType == injectionConstructorType));
                    if (constructorInfo == null)
                    {
                        string message = string.Format("At least one constructor must be decorated with {0} attibute if class has multiple constuctors", injectionConstructorType.GetType().Name);
                        throw new InvalidOperationException(message);
                    }
                }
                else
                {
                    constructorInfo = constructorsInfo.First();
                }
                this.reflectionCache.Add(type, constructorInfo);
            }
            return this.reflectionCache[type];
        }
    }       
}
